Completed
Push — master ( f48291...a65f2c )
by greg
74:59
created

sql.js ➔ ... ➔ ???   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
c 0
b 0
f 0
nc 4
dl 0
loc 14
rs 9.2
nop 1
1
import {parse} from 'node-sqlparser'
2
import {Promise} from 'es6-promise'
3
import path from 'path'
4
import {
5
  coreUtils,
6
  config,
7
  Manager,
8
  cmsData
9
} from '../../'
10
11
/**
12
 * take a string and json to escape sql character and convert to sql like syntax
13
 *
14
 * Example: escapeAbeValuesFromStringRequest('select title from ./ where `abe_meta.template`=`{{article}}`', {article: "test"})
15
 *
16
 * Return string: select title from ___abe_dot______abe_dot______abe___ where  `abe_meta.template`=`test`
17
 * 
18
 * 
19
 * @param  {String} str      raw abe request sql string
20
 * @param  {Object} jsonPage json object of post
21
 * @return {String}          escaped string
22
 */
23
export function escapeAbeValuesFromStringRequest(str, jsonPage) {
0 ignored issues
show
Unused Code introduced by
The parameter jsonPage is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
24
  var matchFrom = /from .(.*?) /
25
  var matchVariable = /{{([a-zA-Z]*)}}/
26
27
  var matchFromExec = matchFrom.exec(str)
28
  if(matchFromExec != null && matchFromExec[1] != null) {
29
30
    var fromMatch
31
    var toReplace = matchFromExec[1]
32
    while (fromMatch = matchVariable.exec(toReplace)) {
33
      try {
34
        var value = eval('jsonPage.' + fromMatch[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
35
        if(value != null) {
36
          toReplace = toReplace.replace('{{' + fromMatch[1] + '}}', value)
37
        }else {
38
          toReplace = toReplace.replace('{{' + fromMatch[1] + '}}', '')
39
        }
40
      }catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
41
      }
42
    }
43
44
    str = str.replace(matchFromExec[1], toReplace)
45
  }
46
47
  var from = /from ([\S\s]+)/.exec(str)
48
49
  var matches = from
50
  if(matches[1]) {
51
    var res = matches[1]
52
    var splitAttr = [' where ', ' order by ', ' limit ', ' WHERE ', ' ORDER BY ', ' LIMIT ']
53
    for(var i = 0; i < splitAttr.length; i++) {
54
      if(res.indexOf(splitAttr[i]) > -1) {
55
        res = res.substring(0, res.indexOf(splitAttr[i]))
56
      }
57
    }
58
    var escapedFrom = res.replace(/\//g, '___abe___')
59
    escapedFrom = escapedFrom.replace(/\./g, '___abe_dot___')
60
    escapedFrom = escapedFrom.replace(/-/g, '___abe_dash___')
61
    str = str.replace(res, escapedFrom)
62
  }
63
64
  str = str.replace(/``/g, '\'\'')
65
66
  return str
67
}
68
69
/**
70
 * analyse and create an object from request sql string
71
 *
72
 * Example: handleSqlRequest('select title from ./ where `abe_meta.template`=`{{article}}`', {article: "test"})
73
 * 
74
 * @param  {String} str      Sql string request
75
 * @param  {Object} jsonPage json of post
76
 * @return {Object}          {type, columns, from, where, string, limit, orderby}
77
 */
78
export function handleSqlRequest(str, jsonPage) {
79
  var req = escapeAbeValuesFromStringRequest(str, jsonPage)
80
  var request = parse(req)
81
  var reconstructSql = ''
82
83
  // SQL TYPE
84
  var type = ''
85
  if(request.type != null) {
86
    type = request.type
87
  }
88
  reconstructSql += `${type} `
89
90
  // SQL COLUMNS
91
  var columns = []
92
  if(request.columns != null) {
93
    if(request.columns === '*') {
94
      columns.push('*')
95
    }else {
96
      Array.prototype.forEach.call(request.columns, (item) => {
97
        columns.push(item.expr.column)
98
      })
99
    }
100
  }
101
  reconstructSql += `${JSON.stringify(columns)} `
102
103
  // SQL FROM
104
  var from = []
105
  if(request.from != null) {
106
107
    Array.prototype.forEach.call(request.from, (item) => {
108
      from.push(item.table)
109
    })
110
  }else {
111
    from.push('*')
112
  }
113
  reconstructSql += `from ${JSON.stringify(from)} `
114
115
  var where
116
  if(request.where != null) {
117
    where = request.where
118
  }
119
120
  var limit = -1
121
  if(request.limit != null) {
122
    limit = request.limit[request.limit.length - 1].value
123
  }
124
125
  var orderby
126
  if(request.orderby != null && request.orderby.length > 0) {
127
    orderby = {
128
      column: request.orderby[0].expr.column,
129
      type: request.orderby[0].type
130
    }
131
    reconstructSql += `ORDER BY ${orderby.column} ${orderby.type} `
132
  }
133
134
  return {
135
    type: type,
136
    columns: columns,
137
    from: from,
138
    where: where,
0 ignored issues
show
Bug introduced by
The variable where does not seem to be initialized in case request.where != null on line 116 is false. Are you sure this can never be the case?
Loading history...
139
    string: reconstructSql,
140
    limit: limit,
141
    orderby: orderby
0 ignored issues
show
Bug introduced by
The variable orderby does not seem to be initialized in case request.orderby != null ...uest.orderby.length > 0 on line 126 is false. Are you sure this can never be the case?
Loading history...
142
  }
143
}
144
145
export function getDataSource(str) {
146
  var res = str.substring(str.indexOf('source=') + 8, str.length)
147
148
  var reg = /([^'"]*=[\s\S]*?}})/g
149
  var matches = res.match(reg)
150
  if(matches != null) {
151
    Array.prototype.forEach.call(matches, (match) => {
152
      res = res.replace(match, '')
153
    })
154
  }else {
155
    res = res.replace('}}', '')
156
  }
157
158
  return res.substring(0, res.length-1)
159
}
160
161
/**
162
 * replaces escaped characters with the right ones
163
 * @param  {String} statement the from clause
164
 * @return {String}           the from sanitized
165
 */
166
export function sanitizeFromStatement(statement){
167
  var from = ''
168
169
  if(statement != null) {
170
    from = statement[0].replace(/___abe_dot___/g, '.')
171
    from = from.replace(/___abe___/g, '/')
172
    from = from.replace(/___abe_dash___/g, '-')
173
  }
174
175
  return from
176
}
177
178
/**
179
 * calculate the directory to analyze from the from clause
180
 * @param  {String} statement the from clause
181
 * @param  {String} tplPath   the path from the template originator
182
 * @return {string}           the directory to analyze
183
 */
184
export function getFromDirectory(statement, tplPath){
185
  var pathFromDir = ''
186
  if(!tplPath){
187
    tplPath = '/'
188
  }
189
190
  if(statement === '' || statement === '*' || statement === '/') {
191
    pathFromDir = path.join(config.root, config.data.url)
192
  }else if(statement === './') {
193
    pathFromDir = path.join(config.root, config.data.url, tplPath)
194
  }else if(statement.indexOf('/') === 0) {
195
    pathFromDir = path.join(config.root, config.data.url, statement)
196
  }else if(statement.indexOf('/') !== 0) {
197
    pathFromDir = path.join(config.root, config.data.url, tplPath, statement)
198
  }
199
200
  return pathFromDir
201
}
202
203
/**
204
 * sort array of files from where clause
205
 *
206
 * @param  {Array} files
207
 * @param  {Object} orderby {orderby: {column: 'date'}} | {orderby: {column: 'random', type: 'ASC'}}
208
 * @return {Array}         sorted array
209
 */
210
export function executeOrderByClause(files, orderby){
211
  if(orderby != null) {
212
    if(orderby.column.toLowerCase() === 'random') {
213
      files = coreUtils.sort.shuffle(files)
214
    }else if(orderby.column.toLowerCase() === 'date') {
215
      if(orderby.type === 'ASC') {
216
        files.sort(coreUtils.sort.byDateAsc)
217
      }else if(orderby.type === 'DESC') {
218
        files.sort(coreUtils.sort.byDateDesc)
219
      }
220
    }
221
  }
222
223
  return files
224
}
225
226
/**
227
 * Check array of files have path that match path statement
228
 *
229
 * executeFromClause(['/'], ['/'])
230
 *
231
 * @param  {Array} statement      paths
232
 * @param  {Array} pathFromClause paths
233
 * @return {Array}                files
234
 */
235
export function executeFromClause(statement, pathFromClause){
236
  var from = sanitizeFromStatement(statement)
237
238
  // if the from clause ends with a dot, we won't recurse the directory analyze
239
  if(from.slice(-1) === '.'){
240
    from = from.slice(0, -1)
241
  }
242
  
243
  var fromDirectory = getFromDirectory(from, pathFromClause)
244
245
  var list = Manager.instance.getList()
246
  var files_array = list.filter((element) => {
247
    if(element.publish) {
248
      if (element.path.indexOf(fromDirectory) > -1) {
249
        return true
250
      }
251
    }
252
    return false
253
  })
254
  return files_array
255
}
256
257
/**
258
 * Execute sql query like to find abe json post that match the query
259
 * 
260
 * @param  {Array} pathQuery of paths
261
 * @param  {String} match     request sql
262
 * @param  {Object} jsonPage  json of post
263
 * @return {Array}           found object that match
264
 */
265
export function execQuery(pathQuery, match, jsonPage) {
266
  var res
267
  var files
268
  var request = handleSqlRequest(cmsData.regex.getAttr(match, 'source'), jsonPage)
269
270
  files = executeFromClause(request.from, pathQuery)
271
  files = executeOrderByClause(files, request.orderby)
272
  res = executeWhereClause(files, request.where, request.limit, request.columns, jsonPage)
273
274
  return res
275
}
276
277
export function executeQuerySync(pathQuerySync, match, jsonPage) {
278
  return execQuery(pathQuerySync, match, jsonPage)
279
}
280
281
export function executeQuery(pathexecuteQuery, match, jsonPage) {
282
  var p = new Promise((resolve) => {
283
    var res = execQuery(pathexecuteQuery, match, jsonPage)
284
    resolve(res)
285
  }).catch(function(e) {
286
    console.error(e)
287
  })
288
289
  return p
290
}
291
292
/**
293
 * check if a given string an url, string json, file url, abe sql request
294
 * 
295
 * get('http://google.com')
296
 * get('{"test":"test"}')
297
 * get('select * from ../')
298
 * get('test')
299
 * 
300
 * @param  {String} str 
301
 * @return {String} url | request | value | file | other
302
 */
303
export function getSourceType(str) {
304
  if(/http:\/\/|https:\/\//.test(str)) {
305
    return 'url'
306
  }
307
308
  if(/select[\S\s]*?from/.test(str)) {
309
    return 'request'
310
  }
311
312
  try {
313
    JSON.parse(str)
314
    return 'value'
315
  }catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
316
317
  }
318
319
  if(/\.json/.test(str)) {
320
    return 'file'
321
  }
322
323
  return 'other'
324
}
325
326
/**
327
 * return array of post that match sql where statement
328
 *
329
 * Example: handleSqlRequest('select title from ./ where `abe_meta.template`=`article`', {})
330
 *
331
 * @param  {Array} files    
332
 * @param  {Object} wheres   clause
333
 * @param  {Int} maxLimit 
334
 * @param  {Array} columns  sql
335
 * @param  {Object} jsonPage json post
336
 * @return {Array}          of files
337
 */
338
export function executeWhereClause(files, wheres, maxLimit, columns, jsonPage){
339
  var res = []
340
  var limit = 0
341
342
  for(let file of files) {
343
    if(limit < maxLimit || maxLimit === -1) {
344
      if(wheres != null) {
345
        if(file.publish && !recurseWhere(wheres, file.publish, jsonPage)) {
346
          var json = JSON.parse(JSON.stringify(file.publish))
347
          var jsonValues = {}
348
349
          if(columns != null && columns.length > 0 && columns[0] !== '*') {
350
            Array.prototype.forEach.call(columns, (column) => {
351
              if(json[column] != null) {
0 ignored issues
show
Bug introduced by
The variable json is changed as part of the for-each loop for example by JSON.parse(JSON.stringify(file.publish)) on line 346. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
352
                jsonValues[column] = json[column]
0 ignored issues
show
Bug introduced by
The variable jsonValues is changed as part of the for-each loop for example by {} on line 347. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
353
              }
354
            })
355
            jsonValues['abe_meta'] = json['abe_meta']
356
          }else {
357
            jsonValues = json
358
          }
359
360
          res.push(jsonValues)
361
          limit++
362
        }
363
      }
364
    } else {
365
      break
366
    }
367
  }
368
369
  return res
370
}
371
372
/**
373
 * Compare where left and where right clause
374
 * 
375
 * @param  {Object} where           clause
376
 * @param  {Object} jsonDoc         json of current post
377
 * @param  {Object} jsonOriginalDoc json of post to compare
378
 * @return {Object}                 {left: value, right: value}
379
 */
380
export function getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc) {
0 ignored issues
show
Unused Code introduced by
The parameter jsonOriginalDoc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter jsonDoc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
381
  var regexIsVariable = /^{{(.*)}}$/
382
  var value
383
  var compare
384
385
  try {
386
    var variableLeft = where.left.column
387
    var checkIfLeftIsAVariable = regexIsVariable.exec(variableLeft)
388
    if(checkIfLeftIsAVariable != null && checkIfLeftIsAVariable.length > 0) {
389
      variableLeft = checkIfLeftIsAVariable[1]
390
    }
391
    value = eval('jsonDoc.' + variableLeft)
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
392
  }catch(e) {
393
    // console.log('e', e)
394
  }
395
  
396
  if(where.operator === 'IN' || where.operator === 'NOT IN') {
397
    compare = []
398
    Array.prototype.forEach.call(where.right.value, (right) => {
399
      var matchRightVariable = regexIsVariable.exec(right.column)
400
      if(matchRightVariable != null && matchRightVariable.length > 0) {
401
        try {
402
          var jsonOriginalValues = eval('jsonOriginalDoc.' + matchRightVariable[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
403
          Array.prototype.forEach.call(jsonOriginalValues, (jsonOriginalValue) => {
0 ignored issues
show
Unused Code introduced by
The parameter jsonOriginalValue is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
404
            compare.push(eval('jsonOriginalValue.' + where.left.column))
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
405
          })
406
        }catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
407
      }
408
      else{
409
        compare.push(right.column)
410
      }
411
    })
412
  } else {
413
    if(where.right.column != null) {
414
      compare = where.right.column
415
    } else if(where.right.value != null) {
416
      compare = where.right.value
417
    }
418
419
    var matchRightVariable = regexIsVariable.exec(compare)
0 ignored issues
show
Bug introduced by
The variable compare does not seem to be initialized in case where.right.value != null on line 415 is false. Are you sure the function exec handles undefined variables?
Loading history...
420
421
    if(matchRightVariable != null && matchRightVariable.length > 0) {
422
      try {
423
        var shouldCompare = eval('jsonOriginalDoc.' + matchRightVariable[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
424
        if(shouldCompare != null) {
425
          compare = shouldCompare
426
        }else {
427
          compare = null
428
        }
429
      }catch(e) {
430
        compare = null
431
      }
432
    }
433
  }
434
435
  return {
436
    left: value,
0 ignored issues
show
Bug introduced by
The variable value does not seem to be initialized in case var variableLeft = where.left.column on line 386 throws an error. Are you sure this can never be the case?
Loading history...
437
    right: compare
438
  }
439
}
440
441
/**
442
 * Check where.left value that match where operator (=, !=, >, >=, <, <=, LIKE, NOT LIKE, AND, OR, IN, NOT IN)
443
 * if operator AND or OR
444
 * Recurse on where.left and where.right sql clause
445
 *
446
 * 
447
 * @param  {Object} where           clause
448
 * @param  {Object} jsonDoc         json of current post
449
 * @param  {Object} jsonOriginalDoc json of post to compare
450
 * @return {Boolean}                 true if not matching | false if matching
451
 */
452
export function recurseWhere(where, jsonDoc, jsonOriginalDoc) {
453
  var isNotLeftCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotLeftCorrect seems to be never used. Consider removing it.
Loading history...
454
  var isNotRightCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotRightCorrect seems to be never used. Consider removing it.
Loading history...
455
  var isNotCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotCorrect seems to be never used. Consider removing it.
Loading history...
456
  var values
457
458
  switch(where.operator) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
459
  case '=':
460
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
461
    isNotCorrect = !(values.left === values.right)
462
    break
463
  case '!=':
464
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
465
    isNotCorrect = !(values.left !== values.right)
466
    break
467
  case '>':
468
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
469
    isNotCorrect = !(values.left > values.right)
470
    break
471
  case '>=':
472
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
473
    isNotCorrect = !(values.left >= values.right)
474
    break
475
  case '<':
476
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
477
    isNotCorrect = !(values.left < values.right)
478
    break
479
  case '<=':
480
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
481
    isNotCorrect = !(values.left <= values.right)
482
    break
483
  case 'LIKE':
484
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
485
    isNotCorrect = !(values.left && values.left.indexOf(values.right) > -1)
486
    break
487
  case 'NOT LIKE':
488
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
489
    isNotCorrect = !(values.left && values.left.indexOf(values.right) === -1)
490
    break
491
  case 'AND':
492
    isNotLeftCorrect = recurseWhere(where.left, jsonDoc, jsonOriginalDoc)
493
    isNotRightCorrect = recurseWhere(where.right, jsonDoc, jsonOriginalDoc)
494
    isNotCorrect = (isNotLeftCorrect || isNotRightCorrect) ? true : false
495
    break
496
  case 'OR':
497
    isNotLeftCorrect = recurseWhere(where.left, jsonDoc, jsonOriginalDoc)
498
    isNotRightCorrect = recurseWhere(where.right, jsonDoc, jsonOriginalDoc)
499
    isNotCorrect = (isNotLeftCorrect && isNotRightCorrect) ? true : false
500
    break
501
  case 'IN':
502
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
503
    isNotCorrect = true
504
    Array.prototype.forEach.call(values.right, (right) => {
505
      if(values.left === right) {
506
        isNotCorrect = false
507
      }
508
    })
509
    break
510
  case 'NOT IN':
511
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
512
    isNotCorrect = false
513
    Array.prototype.forEach.call(values.right, (right) => {
514
      if(values.left === right) {
515
        isNotCorrect = true
516
      }
517
    })
518
    break
519
  }
520
  return isNotCorrect
521
}